package com.ophiux.utils.parser.entity;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class EntityConvert {
	private static final Map<String, Map<String, Field>> cacheFields = new ConcurrentHashMap<String, Map<String, Field>>();
	private static final Set<Class> basicClass = new HashSet<Class>();
	static {
		basicClass.add(Integer.class);
		basicClass.add(Character.class);
		basicClass.add(Byte.class);
		basicClass.add(Float.class);
		basicClass.add(Double.class);
		basicClass.add(Boolean.class);
		basicClass.add(Long.class);
		basicClass.add(Short.class);
		basicClass.add(String.class);
		basicClass.add(BigDecimal.class);
	}

	public static Object translate(Object orig, Type type) {
		try {
			Object target = type.getClass().newInstance();

			Class targetClass = type.getClass();

			Field[] fields = orig.getClass().getDeclaredFields();
			for (Field field : fields) {
				if (isStatic(field))
					continue;
				Field targetField = getTargetField(targetClass, field.getName());
				if (targetField == null)
					continue;
				Object value = getFiledValue(field, orig);
				if (value == null)
					continue;
				Class type1 = field.getType();
				Class type2 = targetField.getType();
				boolean sameType = type1.equals(type2);
				if (isBasicType(type1)) {
					if (sameType)
						setFieldValue(targetField, target, value);
				} else if (value instanceof Map && Map.class.isAssignableFrom(type2)) {
					setMap((Map) value, field, targetField, target);
				} else if (value instanceof Set && Set.class.isAssignableFrom(type2)) {
					setCollection((Collection) value, field, targetField, target);
				} else if (value instanceof List && List.class.isAssignableFrom(type2)) {
					setCollection((Collection) value, field, targetField, target);
				} else if (value instanceof Enum && Enum.class.isAssignableFrom(type2)) {
					setEnum((Enum) value, field, targetField, target);
				} else if (value instanceof java.util.Date && java.util.Date.class.isAssignableFrom(type2)) {
					setDate((Date) value, targetField, type2, target, sameType);
				}
			}
			return target;
		} catch (Throwable t) {
			throw new RuntimeException(t.getMessage());
		}
	}

	public static <T> T translate(Object orig, Class<T> targetClass) {
		try {
			T target = targetClass.newInstance();
			// Field[] fields = null;//orig.getClass().getDeclaredFields();

			List<Field> fields = new ArrayList<Field>();
			Class<?> temp_clzz = orig.getClass();
			while (temp_clzz != null && temp_clzz != Object.class) {
				fields.addAll(Arrays.asList(temp_clzz.getClass().getSuperclass().getDeclaredFields()));
				fields.addAll(Arrays.asList(temp_clzz.getDeclaredFields()));
				temp_clzz = temp_clzz.getSuperclass();
			}

			for (Field field : fields) {
				if (isStatic(field))
					continue;
				Field targetField = getTargetField(targetClass, field.getName());
				if (targetField == null)
					continue;
				Object value = getFiledValue(field, orig);
				if (value == null)
					continue;
				Class type1 = field.getType();
				Class type2 = targetField.getType();
				boolean sameType = type1.equals(type2);
				if (isBasicType(type1)) {
					if (sameType)
						setFieldValue(targetField, target, value);
				} else if (value instanceof Map && Map.class.isAssignableFrom(type2)) {// ��map
					setMap((Map) value, field, targetField, target);
				} else if (value instanceof Set && Set.class.isAssignableFrom(type2)) {// ��set
					setCollection((Collection) value, field, targetField, target);
				} else if (value instanceof List && List.class.isAssignableFrom(type2)) {// ��list
					setCollection((Collection) value, field, targetField, target);
				} else if (value instanceof Enum && Enum.class.isAssignableFrom(type2)) {// ��enum
					setEnum((Enum) value, field, targetField, target);
				} else if (value instanceof java.util.Date && java.util.Date.class.isAssignableFrom(type2)) {// ���������ͣ���������joda��֮�����չʱ�䣬������calendar
					setDate((Date) value, targetField, type2, target, sameType);
				} else {
					setFieldValue(targetField, target, translate(value, (Class) type2));
				}
			}
			return target;
		} catch (Throwable t) {
			throw new RuntimeException(t.getMessage());
		}
	}

	private static Object getFiledValue(Field field, Object obj) throws IllegalAccessException {
		boolean access = field.isAccessible();
		try {
			field.setAccessible(true);
			return field.get(obj);
		} finally {
			field.setAccessible(access);
		}
	}

	private static void setFieldValue(Field field, Object obj, Object value) throws IllegalAccessException {
		/*
		 * boolean access = field.isAccessible(); try {
		 * field.setAccessible(true); field.set(obj, value); } finally {
		 * field.setAccessible(access); }
		 */
		String fieldSetName = parSetName(field.getName());
		try {

			Method fieldSetMethod = obj.getClass().getMethod(fieldSetName, field.getType());
			fieldSetMethod.invoke(obj, value);

		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 拼接在某属性的 set方法
	 * 
	 * @param fieldName
	 * @return String
	 */
	public static String parSetName(String fieldName) {
		if (null == fieldName || "".equals(fieldName)) {
			return null;
		}
		int startIndex = 0;
		if (fieldName.charAt(0) == '_')
			startIndex = 1;
		return "set" + fieldName.substring(startIndex, startIndex + 1).toUpperCase()
				+ fieldName.substring(startIndex + 1);
	}

	public static <T> List<T> translates(List orig, Class<T> targetClass) {
		List<T> list = new ArrayList<T>(orig.size());
		for (Object object : orig) {
			list.add(translate(object, targetClass));
		}
		return list;
	}

	private static <T> void setMap(Map value, Field origField, Field targetField, T targetObject)
			throws IllegalAccessException, InstantiationException {
		Type origType = origField.getGenericType();
		Type targetType = targetField.getGenericType();
		if (origType instanceof ParameterizedType && targetType instanceof ParameterizedType) {
			ParameterizedType origParameterizedType = (ParameterizedType) origType;
			Type[] origTypes = origParameterizedType.getActualTypeArguments();
			ParameterizedType targetParameterizedType = (ParameterizedType) targetType;
			Type[] targetTypes = targetParameterizedType.getActualTypeArguments();
			if (origTypes != null && origTypes.length == 2 && targetTypes != null && targetTypes.length == 2) {
				Class clazz = (Class) origTypes[1];
				if (!isBasicType(clazz) && !clazz.equals(targetTypes[1])) {
					Set<Map.Entry> entries = value.entrySet();
					Map targetMap = value.getClass().newInstance();
					for (Map.Entry entry : entries) {
						targetMap.put(entry.getKey(), translate(entry.getValue(), (Class) targetTypes[1]));
					}
					setFieldValue(targetField, targetObject, targetMap);
					return;
				}
			}
		}
		setFieldValue(targetField, targetObject, value);
	}

	private static <T> void setCollection(Collection value, Field origField, Field targetField, T targetObject)
			throws IllegalAccessException, InstantiationException {
		Type origType = origField.getGenericType();
		Type targetType = targetField.getGenericType();
		if (origType instanceof ParameterizedType && targetType instanceof ParameterizedType) {
			ParameterizedType origParameterizedType = (ParameterizedType) origType;
			Type[] origTypes = origParameterizedType.getActualTypeArguments();
			ParameterizedType targetParameterizedType = (ParameterizedType) targetType;
			Type[] targetTypes = targetParameterizedType.getActualTypeArguments();
			if (origTypes != null && origTypes.length == 1 && targetTypes != null && targetTypes.length == 1) {
				Class clazz = (Class) origTypes[0];
				if (!isBasicType(clazz) && !clazz.equals(targetTypes[0])) {
					Collection collection = value.getClass().newInstance();
					for (Object obj : value) {
						collection.add(translate(obj, (Class) targetTypes[0]));
					}
					setFieldValue(targetField, targetObject, collection);
					return;
				}
			}
		}
		setFieldValue(targetField, targetObject, value);
	}

	private static <T> void setEnum(Enum value, Field origField, Field targetField, T targetObject) throws Exception {
		if (origField.equals(targetField)) {
			setFieldValue(targetField, targetObject, value);
		} else {
			Method method = targetField.getType().getMethod("valueOf", String.class);
			setFieldValue(targetField, targetObject, method.invoke(null, value.toString()));
		}
	}

	private static <T> void setDate(Date value, Field targetField, Class targetFieldType, T targetObject,
			boolean sameType) throws IllegalAccessException {
		Date date = null;
		if (sameType) {
			date = value;
		} else if (targetFieldType.equals(java.sql.Date.class)) {
			date = new java.sql.Date(value.getTime());
		} else if (targetFieldType.equals(java.util.Date.class)) {
			date = new Date(value.getTime());
		} else if (targetFieldType.equals(java.sql.Timestamp.class)) {
			date = new java.sql.Timestamp(value.getTime());
		}
		setFieldValue(targetField, targetObject, date);
	}

	public static Field getTargetField(Class clazz, String fieldName) {
		String classKey = clazz.getName();
		Map<String, Field> fieldMap = cacheFields.get(classKey);
		if (fieldMap == null) {
			fieldMap = new HashMap<String, Field>();
			Field[] fields = clazz.getDeclaredFields();
			for (Field field : fields) {
				if (isStatic(field))
					continue;
				fieldMap.put(field.getName(), field);
			}
			cacheFields.put(classKey, fieldMap);
		}
		return fieldMap.get(fieldName);
	}

	public static boolean isBasicType(Class clazz) {
		return clazz.isPrimitive() || basicClass.contains(clazz);
	}

	public static boolean isStatic(Field field) {
		return (8 & field.getModifiers()) == 8;
	}
}
